package com.zhixing.reward.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhixing.common.domain.entity.CouponUseInfo;
import com.zhixing.common.exception.ZhixingException;
import com.zhixing.reward.domain.dto.QueryParams;
import com.zhixing.reward.domain.entity.Coupon;
import com.zhixing.reward.domain.entity.CouponRecord;
import com.zhixing.reward.domain.enums.CouponCategory;
import com.zhixing.reward.domain.enums.CouponStatus;
import com.zhixing.reward.domain.vo.CouponRecordVO;
import com.zhixing.reward.domain.vo.CouponVO;
import com.zhixing.reward.mapper.CouponMapper;
import com.zhixing.reward.mapper.CouponRecordMapper;
import com.zhixing.reward.service.CouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.zhixing.common.constant.Constant.*;
import static com.zhixing.common.domain.enums.HttpStatus.*;

@Service
@RequiredArgsConstructor
public class CouponServiceImpl implements CouponService {

    private final CouponMapper couponMapper;
    private final CouponRecordMapper couponRecordMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    public List<Coupon> getCouponByCategory(CouponCategory couponCategory) {
        String key = couponCategory == CouponCategory.NEWCOMER_COUPON ? ZHIXING_COUPON_NEWCOMER : ZHIXING_COUPON_HOMEPAGE;
        List<Coupon> coupons = Objects.requireNonNull(redisTemplate.opsForList().range(key, 0, -1))
                .stream().map(o -> (Coupon) o).toList();
        if (CollUtil.isEmpty(coupons)) {
            LambdaQueryWrapper<Coupon> lqw = new LambdaQueryWrapper<Coupon>()
                    .eq(Coupon::getCouponCategory, couponCategory);
            coupons = couponMapper.selectList(lqw);
            redisTemplate.opsForList().rightPushAll(key, coupons.toArray(new Coupon[0]));
            redisTemplate.expire(key, ONE_MONTH, TimeUnit.DAYS);
        }
        return coupons;
    }

    private boolean isTodayExpire() {
        return LocalTime.now().isBefore(LocalTime.ofSecondOfDay(ONE_DAY_SECOND - FIFTEEN_MINUTES * 60));
    }

    @Override
    public void sendNewcomerCoupon(Integer userId) {
        // 查询新人券
        List<Coupon> coupons = getCouponByCategory(CouponCategory.NEWCOMER_COUPON);

        // 发放新人券
        List<CouponRecord> couponRecords = coupons.stream()
                .map(coupon -> new CouponRecord(null, userId, coupon.getId(), LocalDate.now().plusDays(coupon.getValidDays()), CouponStatus.UNUSED))
                .toList();
        couponRecordMapper.insert(couponRecords);
    }

    @Override
    public List<CouponVO> getHomePageCoupon(Integer userId) {
        // 查询首页券
        List<Coupon> coupons = getCouponByCategory(CouponCategory.HOMEPAGE_COUPON);

        // 查询用户是否领取过优惠券
        // 1.从缓存中查询用户是否领取过优惠券
        String key = ZHIXING_COUPON_HOMEPAGE + ":" + userId;
        Map<Integer, Boolean> map = redisTemplate.opsForHash().entries(key).entrySet().stream()
                .collect(Collectors.toMap(entry -> (Integer) entry.getKey(), entry -> (Boolean) entry.getValue()));
        boolean isNoCache = CollUtil.isEmpty(map);
        Set<Integer> unusedCouponIds;
        // 2.如果缓存中没有，则查询数据库
        if (isNoCache) {
            LambdaQueryWrapper<CouponRecord> lqw = new LambdaQueryWrapper<CouponRecord>()
                    .eq(CouponRecord::getUserId, userId).in(CouponRecord::getCouponId, coupons.stream().map(Coupon::getId).toList())
                    .ge(CouponRecord::getExpireDate, LocalDate.now()).eq(CouponRecord::getCouponStatus, CouponStatus.UNUSED);
            unusedCouponIds = couponRecordMapper.selectList(lqw).stream().map(CouponRecord::getCouponId).collect(Collectors.toSet());
        }
        else unusedCouponIds = new HashSet<>();
        List<CouponVO> couponVOList = coupons.stream().map(coupon -> {
            if (isNoCache) map.put(coupon.getId(), unusedCouponIds.contains(coupon.getId()));
            CouponVO couponVO = BeanUtil.copyProperties(coupon, CouponVO.class);
            couponVO.setIsReceived(map.get(coupon.getId()));
            return couponVO;
        }).toList();
        // 3.如果缓存中没有，则将map存入缓存中
        if (isNoCache && isTodayExpire()) {
            redisTemplate.opsForHash().putAll(key, map);
            redisTemplate.expire(key, FIFTEEN_MINUTES, TimeUnit.MINUTES);
        }
        return couponVOList;
    }

    @Override
    public List<CouponRecordVO> getUnusedCoupon(Integer userId) {
        // 查询缓存
        String key = ZHIXING_COUPON_UNUSED + userId;
        List<CouponRecordVO> couponRecordVOList = Objects.requireNonNull(redisTemplate.opsForList().range(key, 0, -1))
                .stream().map(o -> (CouponRecordVO) o).toList();
        if (CollUtil.isNotEmpty(couponRecordVOList)) return couponRecordVOList;

        // 查询数据库
        couponRecordVOList = couponMapper.getUnusedCoupon(userId);
        if (isTodayExpire()) {
            redisTemplate.opsForList().rightPushAll(key, couponRecordVOList.toArray(new CouponRecordVO[0]));
            redisTemplate.expire(key, FIFTEEN_MINUTES, TimeUnit.MINUTES);
        }
        return couponRecordVOList;
    }

    @Override
    public List<CouponRecordVO> getInvalidCouponByPage(Integer userId, QueryParams queryParams) {
        // 查询缓存
        String key = ZHIXING_COUPON_INVALID + userId;
        List<CouponRecordVO> couponRecordVOList = (List<CouponRecordVO>) redisTemplate.opsForHash().get(key, queryParams.getPage());
        if (CollUtil.isNotEmpty(couponRecordVOList)) return couponRecordVOList;

        // 查询数据库
        couponRecordVOList = couponMapper.getInvalidCouponByPage(queryParams, userId);
        if (isTodayExpire()) {
            redisTemplate.opsForHash().put(key, queryParams.getPage(), couponRecordVOList);
            redisTemplate.expire(key, FIFTEEN_MINUTES, TimeUnit.MINUTES);
        }
        return couponRecordVOList;
    }

    @Override
    public void receiveCoupon(Integer userId, Integer couponId) {
        // 查询是否有此优惠券
        Coupon coupon = couponMapper.selectById(couponId);
        if (ObjectUtil.isEmpty(coupon)) throw new ZhixingException(COUPON_NOT_EXIST);

        // 查询是否领取了此优惠券
        LambdaQueryWrapper<CouponRecord> lqw = new LambdaQueryWrapper<CouponRecord>()
                .eq(CouponRecord::getUserId, userId).eq(CouponRecord::getCouponId, couponId)
                .ge(CouponRecord::getExpireDate, LocalDate.now()).eq(CouponRecord::getCouponStatus, CouponStatus.UNUSED);
        CouponRecord couponRecord = couponRecordMapper.selectOne(lqw);
        if (ObjectUtil.isNotEmpty(couponRecord)) throw new ZhixingException(COUPON_ALREADY_RECEIVED);

        // 领取优惠券并删除缓存
        couponRecord = new CouponRecord(null, userId, couponId, LocalDate.now().plusDays(coupon.getValidDays()), CouponStatus.UNUSED);
        couponRecordMapper.insert(couponRecord);
        redisTemplate.delete(List.of(ZHIXING_COUPON_HOMEPAGE + ":" + userId, ZHIXING_COUPON_UNUSED + userId));
    }

    @Override
    public List<CouponRecordVO> getCanUseCouponByAmount(Integer userId, BigDecimal amount) {
        return couponMapper.getCanUseCouponByAmount(userId, amount);
    }

    @Override
    public CouponUseInfo getCouponUseInfoById(Integer couponId, Integer userId) {
        return couponMapper.getCouponUseInfoById(couponId, userId);
    }

    @Override
    public void useCoupon(Integer couponId, Integer userId) {
        // 查询优惠券
        LambdaQueryWrapper<CouponRecord> lqw = new LambdaQueryWrapper<CouponRecord>()
                .eq(CouponRecord::getUserId, userId).eq(CouponRecord::getCouponId, couponId)
                .ge(CouponRecord::getExpireDate, LocalDate.now()).eq(CouponRecord::getCouponStatus, CouponStatus.UNUSED);
        CouponRecord couponRecord = couponRecordMapper.selectOne(lqw);
        if (ObjectUtil.isEmpty(couponRecord)) throw new ZhixingException(COUPON_NOT_EXIST);

        // 更新优惠券状态
        couponRecord.setCouponStatus(CouponStatus.USED);
        couponRecordMapper.updateById(couponRecord);
    }
}
